#include "tut/tut.hpp"
#include "Foo_Enum.hpp"
#include <typeinfo>

namespace tut
{
  using NS1::NS2::Foo_Enum;

  struct Foo_Enum_testdata{};

  typedef test_group<Foo_Enum_testdata> Foo_Enum_testgroup;
  typedef Foo_Enum_testgroup::object Foo_Enum_testobj;
  
  // instantiate
  Foo_Enum_testgroup Foo_Enum_group("Testing the generated Foo_Enum class");

  template<> template<> void Foo_Enum_testobj::
  test<1>()
  {
    // the name is not redundant-- the spec file can
    // optionally choose a different enumerator to be the
    // default value.
    set_test_name("default constructed object is invalid by default");
    NS1::NS2::Foo_Enum foo;
    ensure(foo == NS1::NS2::Foo_Enum::INVALID_VALUE_);

    // proper string type?
    ensure(typeid(Foo_Enum::string_type) == typeid(char const *));

  }

  template<> template<> void Foo_Enum_testobj::
  test<2>()
  {
    set_test_name("Has proper member enumerators");

    ensure_equals("a", Foo_Enum::a.value(), Foo_Enum::raw::a);
    ensure_equals("b", Foo_Enum::b.value(), Foo_Enum::raw::b);
    ensure_equals("c", Foo_Enum::c.value(), Foo_Enum::raw::c);
    ensure_equals("d", Foo_Enum::oh_yeah.value(), Foo_Enum::raw::d);
    ensure_equals("e", Foo_Enum::e.value(), Foo_Enum::raw::e);
    ensure_equals(
        "i", Foo_Enum::INVALID_VALUE_.value(), Foo_Enum::raw::INVALID_);
  }

  template<> template<> void Foo_Enum_testobj::
  test<3>()
  {
    set_test_name("copy ctor");
    Foo_Enum a(Foo_Enum::a);
    Foo_Enum b(Foo_Enum::b);
    Foo_Enum c(Foo_Enum::c);
    Foo_Enum d(Foo_Enum::oh_yeah);
    Foo_Enum e(Foo_Enum::e);
    Foo_Enum i(Foo_Enum::INVALID_VALUE_);

    ensure_equals("a", a, Foo_Enum::a);
    ensure_equals("b", b, Foo_Enum::b);
    ensure_equals("c", c, Foo_Enum::c);
    ensure_equals("d", d, Foo_Enum::oh_yeah);
    ensure_equals("e", e, Foo_Enum::e);
    ensure_equals("i", i, Foo_Enum::INVALID_VALUE_);
  }

  template<> template<> void Foo_Enum_testobj::
  test<4>()
  {
    set_test_name("raw enum ctor");
    Foo_Enum a(Foo_Enum::raw::a);
    Foo_Enum b(Foo_Enum::raw::b);
    Foo_Enum c(Foo_Enum::raw::c);
    Foo_Enum d(Foo_Enum::raw::d);
    Foo_Enum e(Foo_Enum::raw::e);
    Foo_Enum i(Foo_Enum::raw::INVALID_);

    ensure_equals("a", a, Foo_Enum::a);
    ensure_equals("b", b, Foo_Enum::b);
    ensure_equals("c", c, Foo_Enum::c);
    ensure_equals("d", d, Foo_Enum::oh_yeah);
    ensure_equals("e", e, Foo_Enum::e);
    ensure_equals("i", i, Foo_Enum::INVALID_VALUE_);
  }

  template<> template<> void Foo_Enum_testobj::
  test<5>()
  {
    set_test_name("copy operator=");
    Foo_Enum a;
    Foo_Enum b;
    Foo_Enum c;
    Foo_Enum d;
    Foo_Enum e;
    Foo_Enum i;

    a = Foo_Enum::a;
    b = Foo_Enum::b;
    c = Foo_Enum::c;
    d = Foo_Enum::oh_yeah;
    e = Foo_Enum::e;
    i = Foo_Enum::INVALID_VALUE_;

    ensure_equals("a", a, Foo_Enum::a);
    ensure_equals("b", b, Foo_Enum::b);
    ensure_equals("c", c, Foo_Enum::c);
    ensure_equals("d", d, Foo_Enum::oh_yeah);
    ensure_equals("e", e, Foo_Enum::e);
    ensure_equals("i", i, Foo_Enum::INVALID_VALUE_);
  }    

  template<> template<> void Foo_Enum_testobj::
  test<6>()
  {
    set_test_name("raw operator=");
    Foo_Enum a;
    Foo_Enum b;
    Foo_Enum c;
    Foo_Enum d;
    Foo_Enum e;
    Foo_Enum i;

    a = Foo_Enum::raw::a;
    b = Foo_Enum::raw::b;
    c = Foo_Enum::raw::c;
    d = Foo_Enum::raw::d;
    e = Foo_Enum::raw::e;
    i = Foo_Enum::raw::INVALID_;

    ensure_equals("a", a, Foo_Enum::a);
    ensure_equals("b", b, Foo_Enum::b);
    ensure_equals("c", c, Foo_Enum::c);
    ensure_equals("d", d, Foo_Enum::oh_yeah);
    ensure_equals("e", e, Foo_Enum::e);
    ensure_equals("i", i, Foo_Enum::INVALID_VALUE_);
  }    

  template<> template<> void Foo_Enum_testobj::
  test<7>()
  {
    set_test_name("operator==");

    // enumgen/enumgen comps
    ensure("a1", Foo_Enum::a == Foo_Enum::a);
    ensure("b1", Foo_Enum::b == Foo_Enum::b);
    ensure("c1", Foo_Enum::c == Foo_Enum::c);
    ensure("d1", Foo_Enum::oh_yeah == Foo_Enum::oh_yeah);
    ensure("e1", Foo_Enum::e == Foo_Enum::e);
    ensure("i1", Foo_Enum::INVALID_VALUE_ == Foo_Enum::INVALID_VALUE_);

    // Foo.enum assigns values to make c and e be equal
    ensure("c==e", Foo_Enum::c == Foo_Enum::e);

    // raw value tests
    ensure("a2", Foo_Enum::a == Foo_Enum::raw::a);
    ensure("b2", Foo_Enum::b == Foo_Enum::raw::b);
    ensure("c2", Foo_Enum::c == Foo_Enum::raw::c);
    ensure("d2", Foo_Enum::oh_yeah == Foo_Enum::raw::d);
    ensure("e2", Foo_Enum::e == Foo_Enum::raw::e);
    ensure("i2", Foo_Enum::INVALID_VALUE_ == Foo_Enum::raw::INVALID_);

    // symmetry
    ensure("a3", Foo_Enum::raw::a == Foo_Enum::a);
    ensure("b3", Foo_Enum::raw::b == Foo_Enum::b);
    ensure("c3", Foo_Enum::raw::c == Foo_Enum::c);
    ensure("d3", Foo_Enum::raw::d == Foo_Enum::oh_yeah);
    ensure("e3", Foo_Enum::raw::e == Foo_Enum::e);
    ensure("i3", Foo_Enum::raw::INVALID_ == Foo_Enum::INVALID_VALUE_);
  }

  template<> template<> void Foo_Enum_testobj::
  test<8>()
  {
    set_test_name("operator!=");
    // enumgen/enumgen comps
    ensure("a1", Foo_Enum::a != Foo_Enum::b);
    ensure("a2", Foo_Enum::a != Foo_Enum::c);
    ensure("a3", Foo_Enum::a != Foo_Enum::oh_yeah);
    ensure("a4", Foo_Enum::a != Foo_Enum::e);
    ensure("a5", Foo_Enum::a != Foo_Enum::INVALID_VALUE_);

    // raw value tests
    ensure("a1", Foo_Enum::a != Foo_Enum::raw::b);
    ensure("a2", Foo_Enum::a != Foo_Enum::raw::c);
    ensure("a3", Foo_Enum::a != Foo_Enum::raw::d);
    ensure("a4", Foo_Enum::a != Foo_Enum::raw::e);
    ensure("a5", Foo_Enum::e != Foo_Enum::raw::INVALID_);
  }

  template<> template<> void Foo_Enum_testobj::
  test<9>()
  {
    set_test_name("underlying value extractors");

    ensure_equals(
      "a1",
      Foo_Enum::a.value(),
      Foo_Enum::raw::a);
    ensure_equals(
      "b1",
      Foo_Enum::b.value(),
      Foo_Enum::raw::b);
    ensure_equals(
      "c1",
      Foo_Enum::c.value(),
      Foo_Enum::raw::c);
    ensure_equals(
      "d1",
      Foo_Enum::oh_yeah.value(),
      Foo_Enum::raw::d);
    ensure_equals(
      "e1",
      Foo_Enum::e.value(),
      Foo_Enum::raw::e);
    ensure_equals(
      "i1",
      Foo_Enum::INVALID_VALUE_.value(),
      Foo_Enum::raw::INVALID_);
    ensure_equals(
      "a2",
      Foo_Enum::a.as_long(),
      Foo_Enum::raw::a);
    ensure_equals(
      "b2",
      Foo_Enum::b.as_long(),
      Foo_Enum::raw::b);
    ensure_equals(
      "c2",
      Foo_Enum::c.as_long(),
      Foo_Enum::raw::c);
    ensure_equals(
      "d2",
      Foo_Enum::oh_yeah.as_long(),
      Foo_Enum::raw::d);
    ensure_equals(
      "e2",
      Foo_Enum::e.as_long(),
      Foo_Enum::raw::e);
    ensure_equals(
      "i2",
      Foo_Enum::INVALID_VALUE_.as_long(),
      Foo_Enum::raw::INVALID_);

    ensure_equals("a3", Foo_Enum::a.as_long(), 1L);
    ensure_equals("b3", Foo_Enum::b.as_long(), 999L);
    ensure_equals("c3", Foo_Enum::c.as_long(), 3L);
    ensure_equals("d3", Foo_Enum::oh_yeah.as_long(), 5L);
    ensure_equals("e3", Foo_Enum::e.as_long(), 3L);
    ensure_equals(
        "i3", Foo_Enum::INVALID_VALUE_.as_long(), 4L);

    ensure_equals(
        "a4", Foo_Enum::a.value(), Foo_Enum::a.as_long());
    ensure_equals(
        "b4", Foo_Enum::b.value(), Foo_Enum::b.as_long());
    ensure_equals(
        "c4", Foo_Enum::c.value(), Foo_Enum::c.as_long());
    ensure_equals(
        "d4", Foo_Enum::oh_yeah.value(), Foo_Enum::oh_yeah.as_long());
    ensure_equals(
        "e4", Foo_Enum::e.value(), Foo_Enum::e.as_long());
    ensure_equals(
        "i5",
        Foo_Enum::INVALID_VALUE_.value(),
        Foo_Enum::INVALID_VALUE_.as_long());
  }


  template<> template<> void Foo_Enum_testobj::
  test<10>()
  {
    set_test_name("underlying value setters");
    
    Foo_Enum a;
    Foo_Enum b;
    Foo_Enum c;
    Foo_Enum d;
    Foo_Enum e;
    Foo_Enum i[3];

    // setting raw values to objects
    a.set_value_checked(Foo_Enum::raw::a);
    b.set_value_checked(Foo_Enum::raw::b);
    c.set_value_checked(Foo_Enum::raw::c);
    d.set_value_checked(Foo_Enum::raw::d);
    e.set_value_checked(Foo_Enum::raw::e);

    i[0].set_value_checked(Foo_Enum::raw::INVALID_);
    i[1].set_value_checked(static_cast<Foo_Enum::raw::enum_type>(123456));
    i[2].set_value_checked(static_cast<Foo_Enum::raw::enum_type>(-1));

    ensure_equals("a", a, Foo_Enum::a);
    ensure_equals("b", b, Foo_Enum::b);
    ensure_equals("c", c, Foo_Enum::c);
    ensure_equals("d", d, Foo_Enum::oh_yeah);
    ensure_equals("e", e, Foo_Enum::e);
    ensure_equals("i[0]", i[0], Foo_Enum::INVALID_VALUE_);
    ensure_equals("i[1]", i[1], Foo_Enum::INVALID_VALUE_);
    ensure_equals("i[2]", i[2], Foo_Enum::INVALID_VALUE_);

    Foo_Enum a2;
    Foo_Enum b2;
    Foo_Enum c2;
    Foo_Enum d2;
    Foo_Enum e2;
    Foo_Enum i2[3];

    // setting "long int" values to objects
    a2.set_value_checked(long(Foo_Enum::raw::a));
    b2.set_value_checked(long(Foo_Enum::raw::b));
    c2.set_value_checked(long(Foo_Enum::raw::c));
    d2.set_value_checked(long(Foo_Enum::raw::d));
    e2.set_value_checked(long(Foo_Enum::raw::e));
    i2[0].set_value_checked(Foo_Enum::raw::INVALID_);
    i2[1].set_value_checked(static_cast<Foo_Enum::raw::enum_type>(123456));
    i2[2].set_value_checked(static_cast<Foo_Enum::raw::enum_type>(-1));

    ensure_equals("a", a, Foo_Enum::a);
    ensure_equals("b", b, Foo_Enum::b);
    ensure_equals("c", c, Foo_Enum::c);
    ensure_equals("d", d, Foo_Enum::oh_yeah);
    ensure_equals("e", e, Foo_Enum::e);
    ensure_equals("i2[0]", i[0], Foo_Enum::INVALID_VALUE_);
    ensure_equals("i2[1]", i[1], Foo_Enum::INVALID_VALUE_);
    ensure_equals("i2[2]", i[2], Foo_Enum::INVALID_VALUE_);
  }

  template<> template<> void Foo_Enum_testobj::
  test<11>()
  {
    set_test_name("string name() extractors");
    
    // converting an enumgen object to a string returns the
    // primary name.  If no primary name is explicitly set,
    // then it uses the spelling of the enumerator itself.
    ensure_equals("a.name()", Foo_Enum::a.name(), std::string("a"));
    ensure_equals("b.name()", Foo_Enum::b.name(), std::string("b"));
    ensure_equals("c.name()", Foo_Enum::c.name(), std::string("c"));
    ensure_equals(
        "oh_yeah.name()", Foo_Enum::oh_yeah.name(), std::string("oh_yeah"));


    // NOTICE: Foo_Enum::e.name() returns "c" because both e
    // and c enumerators have the same value, and thus are
    // equivalent.  There can be many aliases for a value,
    // but only one name.
    ensure_equals("e.name()", Foo_Enum::e.name(), std::string("c"));
    ensure_equals(
        "INVALID_VALUE_.name()",
        Foo_Enum::INVALID_VALUE_.name(),
        std::string("INVALID_VALUE_"));
  }

  template<> template<> void Foo_Enum_testobj::
  test<12>()
  {
    set_test_name("invalid query");
    ensure("a", !Foo_Enum::a.invalid());
    ensure("b", !Foo_Enum::b.invalid());
    ensure("c", !Foo_Enum::c.invalid());
    ensure("d", !Foo_Enum::oh_yeah.invalid());
    ensure("e", !Foo_Enum::e.invalid());
    ensure("i", Foo_Enum::INVALID_VALUE_.invalid());

    // default constructed
    ensure("default-constructed", Foo_Enum().invalid());
  }

  template<> template<> void Foo_Enum_testobj::
  test<13>()
  {
    set_test_name("name iterators");

    // this iterates over the names of enumerators.  That
    // is, for each name there should be a corresponding
    // static member of type Foo_Enum with the same
    // spelling, representing an instance of this enum
    // object. 
    Foo_Enum::const_name_iterator
        c = Foo_Enum::begin_name(),
        end = Foo_Enum::end_name();

    ensure("name:begin,end", c != end);
    ensure_equals("name:a", *c, std::string("a"));
    ++c;
    ensure("name:not-end 1", c != end);
    ensure_equals("name:b", *c, std::string("b"));    
    ++c;
    ensure("name:not-end 2", c != end);
    ensure_equals("name:c", *c, std::string("c"));
    ++c;
    ensure("name:not-end 3", c != end);
    ensure_equals(
      "name:INVALID_VALUE_", 
      *c, 
      std::string("INVALID_VALUE_"));    
    ++c;
    ensure("name:not-end 4", c != end);
    ensure_equals("name:oh_yeah", *c, std::string("oh_yeah"));    
    ++c;
    ensure("name:not-end 5", c != end);
    ensure_equals("name:e", *c, std::string("e"));    

    ++c;
    ensure("name:at-end 6", c == end);
  }

  template<> template<> void Foo_Enum_testobj::
  test<14>()
  {
    set_test_name("value iterators");

    // this iterates only over the object values

    Foo_Enum::const_value_iterator
        c = Foo_Enum::begin_value(),
        e = Foo_Enum::end_value();

    ensure("value:begin,end", c != e);

    ensure_equals("value:a", *c, Foo_Enum::a);
    ++c;
    ensure("value:not-end 1", c != e);
    ensure_equals("value:b", *c, Foo_Enum::b);
    ++c;
    ensure("value:not-end 2", c != e);
    ensure_equals("value:c", *c, Foo_Enum::c);
    ++c;
    ensure("value:not-end 3", c != e);
    ensure_equals("value:oh_yeah", *c, Foo_Enum::oh_yeah);    
    ++c;
    ensure("value:at-end 4", c == e);
  }

  template<> template<> void Foo_Enum_testobj::
  test<15>()
  {
    set_test_name("alias iterators");

    // test aliases for "a"
    Foo_Enum:: const_name_iterator 
      i = Foo_Enum::a.begin_alias(),
      e = Foo_Enum::a.end_alias();

    ensure("a:1) begin/end !=", i != e);
    ensure("a:alias 1", strcmp (*i, "aye") == 0);
    ++i;
    ensure("a:2) begin/end != ", i != e);
    ensure("a:alias 2", strcmp (*i, "aie") == 0);
    ++i;
    ensure("a:3) begin/end != ", i != e);
    ensure("a:alias 3", strcmp (*i, "ay") == 0);
    ++i;
    ensure("a:4) begin/end == ", i == e);

    // test aliases for "b"
    i = Foo_Enum::b.begin_alias();
    e = Foo_Enum::b.end_alias();
    ensure ("b: no aliases", i == e);
    
    // test aliases for "c" *** NOTE: since 'c' and 'e' are
    // aliases, e's aliases are associated with c's.

    i = Foo_Enum::c.begin_alias();
    e = Foo_Enum::c.end_alias();
    ensure ("c:1 begin/end !=", i != e);
    ensure("c:alias 1", strcmp (*i, "CEE") == 0);    
    ++i;
    ensure ("c:2 begin/end !=", i != e);
    ensure("c:alias 2", strcmp (*i, "cee") == 0);    
    ++i;
    ensure ("c:3 begin/end !=", i != e);
    ensure("c:alias 3", strcmp (*i, "See") == 0);    
    ++i;
    ensure ("c:4 begin/end !=", i != e);
    ensure("e:alias 4", strcmp (*i, "eee") == 0);    
    ++i;
    ensure ("c:5 begin/end !=", i != e);
    ensure("e:alias 5", strcmp (*i, "eeek") == 0);    
    ++i;
    ensure ("c:6 begin/end ==", i == e);

    // test aliases for "d"
    i = Foo_Enum::oh_yeah.begin_alias();
    e = Foo_Enum::oh_yeah.end_alias();
    ensure ("d: no aliases", i == e);

    // test aliases for "e"
    // *** NOTE: EXACT SAME CASES AS FOR C, SINCE THEY ARE
    // COMBINED.
    i = Foo_Enum::e.begin_alias();
    e = Foo_Enum::e.end_alias();
    ensure ("e:1 begin/end !=", i != e);
    ensure("e:alias 1", strcmp (*i, "CEE") == 0);    
    ++i;
    ensure ("e:2 begin/end !=", i != e);
    ensure("e:alias 2", strcmp (*i, "cee") == 0);    
    ++i;
    ensure ("e:3 begin/end !=", i != e);
    ensure("e:alias 3", strcmp (*i, "See") == 0);    
    ++i;
    ensure ("e:4 begin/end !=", i != e);
    ensure("e:alias 4", strcmp (*i, "eee") == 0);    
    ++i;
    ensure ("e:5 begin/end !=", i != e);
    ensure("e:alias 5", strcmp (*i, "eeek") == 0);    
    ++i;
    ensure ("e:6 begin/end ==", i == e);
  }

  template<> template<> void Foo_Enum_testobj::
  test<16>()
  {
    set_test_name("contains duplicates");

    // c == e
    ensure(Foo_Enum::contains_duplicates() == true);
  }


  template<> template<> void Foo_Enum_testobj::
  test<17>()
  {
    set_test_name("name_to_value");

    ensure_equals("a", Foo_Enum::name_to_value("a"), Foo_Enum::a);
    ensure_equals("b", Foo_Enum::name_to_value("b"), Foo_Enum::b);
    ensure_equals("c", Foo_Enum::name_to_value("c"), Foo_Enum::c);
    ensure_equals(
        "invalid", 
        Foo_Enum::name_to_value("INVALID_VALUE_"),
        Foo_Enum::INVALID_VALUE_);    
    ensure_equals(
        "oh_yeah",
        Foo_Enum::name_to_value("oh_yeah"),
        Foo_Enum::oh_yeah);

    ensure_equals(
        "d", // replaced by primary name "oh_yeah"
        Foo_Enum::name_to_value("d"),
        Foo_Enum::INVALID_VALUE_);    

    // NOTE: e is not unique, so its name resolves to c's
    ensure_equals("e->c", Foo_Enum::name_to_value("e"), Foo_Enum::c);

    
    ensure_equals("aye", Foo_Enum::name_to_value("aye"), Foo_Enum::a);
    ensure_equals("aie", Foo_Enum::name_to_value("aie"), Foo_Enum::a);
    ensure_equals("ay", Foo_Enum::name_to_value("ay"), Foo_Enum::a);
    ensure_equals(
        "AIE",
        Foo_Enum::name_to_value("AIE"),
        Foo_Enum::INVALID_VALUE_);

    ensure_equals("CEE", Foo_Enum::name_to_value("CEE"), Foo_Enum::c);    
    ensure_equals("cee", Foo_Enum::name_to_value("cee"), Foo_Enum::c);    
    ensure_equals("See", Foo_Enum::name_to_value("See"), Foo_Enum::c);    

    ensure_equals("eee->c", Foo_Enum::name_to_value("eee"), Foo_Enum::c);
    ensure_equals("eeek->c", Foo_Enum::name_to_value("eeek"), Foo_Enum::c);
  }


  template<> template<> void Foo_Enum_testobj::
  test<18>()
  {
    set_test_name("value_to_name");

    ensure_equals(
        "a->'a'",
        strcmp("a", Foo_Enum::value_to_name(Foo_Enum::a)),
        0);
    ensure_equals(
        "b->'b'",
        strcmp("b", Foo_Enum::value_to_name(Foo_Enum::b)),
        0);
    ensure_equals(
        "c->'c'",
        strcmp("c", Foo_Enum::value_to_name(Foo_Enum::c)),
        0);
    ensure_equals(
        "INVALID_VALUE_->INVALID_VALUE_",
        strcmp(
            "INVALID_VALUE_",
            Foo_Enum::value_to_name(Foo_Enum::INVALID_VALUE_)),
        0);
    ensure_equals(
        "oh_yeah->'oh_yeah'",
        strcmp("oh_yeah", Foo_Enum::value_to_name(Foo_Enum::oh_yeah)),
        0);
  }



  bool stream_test(Foo_Enum val)
  {
    ensure(!val.invalid());

    std::stringstream oss;
    oss << val;
    Foo_Enum newval;
    ensure(newval.invalid());
    oss >> newval;

    ensure_equals(val.name(), newval, val);
  }


  template<> template<> void Foo_Enum_testobj::
  test<19>()
  {
    set_test_name("iostream ops");
    stream_test(Foo_Enum::a);
    stream_test(Foo_Enum::b);
    stream_test(Foo_Enum::c);
    stream_test(Foo_Enum::oh_yeah);
    stream_test(Foo_Enum::e);
  }

} // tut
 
